/*******************************************************************************
 * Copyright (c) 2008-2010 Cedric Vidal
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Cedric Vidal - original idea, initial contribution and API
 *******************************************************************************/

package org.trans4j;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import net.sf.cglib.proxy.Factory;

import org.junit.Test;

/**
 * @author <a href="mailto:c.vidal@proxiad.com">Cedric Vidal, ProxiAD Group</a>
 * 
 */
public class ReferenceNotYetTransformedObjectTest {

	@Test
	public void referenceNotYetTransformedObjectReturnsLazyProxy() {
		LeftParent b = new LeftParent();
		b.setLeftName("leftParent");
		b.getLeftChilds().add(createLeftChild("leftChild1", b));
		b.getLeftChilds().add(createLeftChild("leftChild2", b));

		RightParent a = TestMapper.INSTANCE.parent(b);
		RightChild rightChild1 = a.getChild1();
		RightChild rightChild2 = a.getChild2();
		assertEquals("rightChild1", rightChild1.getRightName());
		assertEquals("rightChild2", rightChild2.getRightName());

		assertNotNull(rightChild1);
		assertNotNull(rightChild2);

		RightParent rightParentFromChild1 = rightChild1.getRightParent();
		RightParent rightParentFromChild2 = rightChild2.getRightParent();

		// test identity
		assertTrue(rightParentFromChild1 == a);
		assertTrue(rightParentFromChild2 == a);

		// test equality
		assertEquals(rightParentFromChild1, a);
		assertEquals(rightParentFromChild2, a);

		assertTrue(!rightChild1.equals(rightChild2));
		assertTrue(rightChild1 != rightChild2);

		// check proxies
		assertFalse(isProxy(a.getRightChilds().get(0)));
		assertFalse(isProxy(a.getRightChilds().get(1)));
		assertTrue(isProxy(rightChild1));
		assertTrue(isProxy(rightChild2));

		// test equality between equal but not identical proxied and not proxied
		// objects
		assertTrue(rightChild1 != a.getRightChilds().get(0));
		assertTrue(rightChild2 != a.getRightChilds().get(1));
		assertEquals(rightChild1, a.getRightChilds().get(0));
		assertEquals(rightChild2, a.getRightChilds().get(1));

	}

	public static boolean isProxy(Object object) {
		return (object instanceof Factory);
	}

	@Test
	public void resolveNotYetTransformedObjectThrowsException() {
		LeftParent b = new LeftParent();
		b.setLeftName("b");
		b.getLeftChilds().add(createLeftChild("l1", b));
		b.getLeftChilds().add(createLeftChild("l2", b));

		try {
			RightParent a = TestMapper.INSTANCE.parentResolveUnreturned(b);
		} catch (CannotResolveUnreturnedObjectException e) {
			assertEquals("You can't invoke a method on an object which is currently being transformed. Faulty rule is "
				+ method(TestMapper.class, "parentResolveUnreturned", LeftParent.class), e.getMessage());
		} catch (Exception e) {
			fail("It should have thrown a "
					+ CannotResolveUnreturnedObjectException.class
					+ " but threw " + e);
		}
	}

	@SuppressWarnings("unchecked")
	public Method method(Class clazz, String name, Class<?>... parameterTypes) {
		try {
			return clazz.getMethod(name, parameterTypes);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private LeftChild createLeftChild(String name, LeftParent parent) {
		LeftChild leftChild = new LeftChild();
		leftChild.setLeftName(name);
		leftChild.setLeftParent(parent);
		return leftChild;
	}

	static class TestMapper {
		public static final TestMapper INSTANCE = Trans4j
				.mapper(TestMapper.class);

		public RightParent parentResolveUnreturned(LeftParent b) {
			RightParent a = createParent(b.getLeftName());

			// this is a proxy as 'b' hasn't been transformed yet
			RightParent a2 = parentResolveUnreturned(b);

			// this fails
			String rightName = a2.getRightName();

			return a;
		}

		public RightParent parent(LeftParent b) {
			RightParent a = createParent(b.getLeftName());
			for (LeftChild c : b.getLeftChilds()) {
				a.getRightChilds().add(child(c));
			}
			a.setChild1(child(b.getLeftChilds().get(0)));
			a.setChild2(child(b.getLeftChilds().get(1)));
			return a;
		}

		@CreateRule
		protected RightParent createParent(String leftName) {
			RightParent a = new RightParent();
			a.setRightName(leftName.replace("left", "right"));
			return a;
		}

		public RightChild child(LeftChild b) {
			RightChild a = new RightChild();
			a.setRightName(b.getLeftName().replace("left", "right"));
			a.setRightParent(parent(b.getLeftParent()));
			return a;
		}

	}

	static class RightParent {
		private String rightName;

		public String getRightName() {
			return rightName;
		}

		public void setRightName(String name) {
			this.rightName = name;
		}

		private List<RightChild> rightChilds = new ArrayList<RightChild>();

		public List<RightChild> getRightChilds() {
			return rightChilds;
		}

		public void setRightChilds(List<RightChild> rightChilds) {
			this.rightChilds = rightChilds;
		}

		private RightChild child1;

		public RightChild getChild1() {
			return child1;
		}

		public void setChild1(RightChild firstChild) {
			this.child1 = firstChild;
		}

		private RightChild child2;

		public RightChild getChild2() {
			return child2;
		}

		public void setChild2(RightChild child2) {
			this.child2 = child2;
		}

		@Override
		public boolean equals(Object obj) {
			return super.equals(obj);
		}

	}

	static class RightChild {

		private RightParent rightParent;

		public RightParent getRightParent() {
			return rightParent;
		}

		public void setRightParent(RightParent rightParent) {
			this.rightParent = rightParent;
		}

		private String rightName;

		public String getRightName() {
			return rightName;
		}

		public void setRightName(String name) {
			this.rightName = name;
		}

		@Override
		public boolean equals(Object arg0) {
			return super.equals(arg0);
		}

	}

	static class LeftParent {
		private String leftName;

		public String getLeftName() {
			return leftName;
		}

		public void setLeftName(String name) {
			this.leftName = name;
		}

		private List<LeftChild> leftChilds = new ArrayList<LeftChild>();

		public List<LeftChild> getLeftChilds() {
			return leftChilds;
		}

		public void setLeftChilds(List<LeftChild> leftChilds) {
			this.leftChilds = leftChilds;
		}

		@Override
		public boolean equals(Object obj) {
			return super.equals(obj);
		}

	}

	static class LeftChild {

		private LeftParent leftParent;

		public LeftParent getLeftParent() {
			return leftParent;
		}

		public void setLeftParent(LeftParent parent) {
			this.leftParent = parent;
		}

		private String leftName;

		public String getLeftName() {
			return leftName;
		}

		public void setLeftName(String name) {
			this.leftName = name;
		}
	}

}
